﻿
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;
using Test30.Core;

namespace Test30.Engine
{
    public class ItemScript
    {
        public static ItemScript Create(string script)
        {
            //惰性编译
            UncompiledScript uncompiled = new UncompiledScript(script);
            UncompiledScripts.Add(uncompiled);
            return uncompiled.Script;
        }

        public bool Invoke(Entity user, Item item, Action action, Vec? target)
        {
            // lazy compile if needed
            if (_wrapper == null)
            {
                CompileScripts();
            }

            return _wrapper != null && _wrapper.Invoke(user, item, action, target);
        }
        private static void CompileScripts()
        {
            string code = GenerateScriptClasses();

            string[] referencedAssemblies = new string[]
                {
                    "mscorlib.dll",
                    "Amaranth.Engine.dll",
                    "Amaranth.Util.dll"
                };

            CompilerParameters parameters = new CompilerParameters(referencedAssemblies);
            CSharpCodeProvider provider = new CSharpCodeProvider();

            CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);

            if (results.Errors.Count > 0)
            {
                Console.WriteLine(code);
                foreach (CompilerError error in results.Errors)
                {
                    Console.WriteLine(error.ToString());
                }
            }

            Assembly assembly = results.CompiledAssembly;

            // bind the existing ItemUses to their compiled scripts
            foreach (UncompiledScript script in UncompiledScripts)
            {
                Type type = assembly.GetType("Amaranth.Engine.Compiled." + script.ClassName);

                ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
                if (constructor != null)
                {
                    ItemScriptWrapper wrapper = (ItemScriptWrapper)constructor.Invoke(new object[0]);

                    script.Script._wrapper = wrapper;
                }
            }

            // all compiled now
            UncompiledScripts.Clear();
        }
        private static string GenerateScriptClasses()
        {
            StringBuilder code = new StringBuilder();

            code.AppendLine("using System;");
            code.AppendLine("using System.Collections.Generic;");
            code.AppendLine("using System.Text;");
            code.AppendLine("");

            code.AppendLine("");
            code.AppendLine("namespace Amaranth.Engine.Compiled");
            code.AppendLine("{");

            foreach (UncompiledScript script in UncompiledScripts)
            {
                code.AppendLine("    public class " + script.ClassName + " : ItemScriptWrapper");
                code.AppendLine("    {");
                code.AppendLine("        protected override bool Use()");
                code.AppendLine("        {");
                code.AppendLine("            // script:");
                code.AppendLine("            " + script.SourceCode);
                code.AppendLine("");
                code.AppendLine("            return true;");
                code.AppendLine("        }");
                code.AppendLine("    }");
            }

            code.AppendLine("}");

            return code.ToString();
        }
        private class UncompiledScript
        {
            public string ClassName => $"ItemScript_{_index}";
            public ItemScript Script { get; }
            public string SourceCode{ get; }
            public UncompiledScript(string sourceCode)
            {
                _index = _nextScriptIndex++;
                SourceCode = sourceCode;

                if (!SourceCode.EndsWith(";"))
                {
                    SourceCode += ";";
                }

                Script = new ItemScript();
            }

            private static int _nextScriptIndex = 1;
            private readonly int _index;

        }

        private static readonly List<UncompiledScript> UncompiledScripts = new List<UncompiledScript>();

        private ItemScriptWrapper _wrapper;
    }

    public abstract class ItemScriptWrapper
    {
        public bool Invoke(Entity user, Item item, Action action, Vec? target)
        {
            _entity = user;
            _item = item;
            _action = action;
            _target = target;

            return Use();
        }

        protected abstract bool Use();

        #region Base Item uses

        protected Item Item => _item;

        //### bob: should check type
        protected Hero Hero => (Hero)_entity;

        protected void GainHealth()                     { _action.AddAction(new GainHealthAction(_entity, Item.Attack)); }
        protected void Heal()                           { _action.AddAction(new HealAction(_entity, Item.Attack)); }
        protected void HealFull()                       { _action.AddAction(new HealFullAction(_entity)); }

        protected void Teleport(int distance)           { _action.AddAction(new TeleportAction(_entity, distance)); }
        protected void MakeTownPortal()                 { _action.AddAction(new CreatePortalAction(_entity)); }
        protected void Haste(int boost)                 { _action.AddAction(new HasteAction(_entity, Item.Attack.Roll(), boost)); }
        protected void Light(int radius, string noun)   { _action.AddAction(new LightAction(_entity, new Noun(noun), radius, Item.Attack)); }
        protected void Explode(int radius)              { _action.AddAction(new ExplodeAction(_entity, _item, radius)); }

        protected void DetectFeatures()                 { _action.AddAction(new DetectFeaturesAction(_entity)); }
        protected void DetectItems()                    { _action.AddAction(new DetectItemsAction(_entity)); }

        protected void CurePoison()                     { _action.AddAction(new CurePoisonAction(_entity)); }
        protected void CureDisease()                    { _action.AddAction(new CureDiseaseAction(_entity)); }

        protected void RestoreAll()                     { _action.AddAction(new RestoreAllAction(Hero)); }
        protected void RestoreStrength()                { _action.AddAction(new RestoreAction(Hero, Hero.Stats.Strength)); }
        protected void RestoreAgility()                 { _action.AddAction(new RestoreAction(Hero, Hero.Stats.Agility)); }
        protected void RestoreStamina()                 { _action.AddAction(new RestoreAction(Hero, Hero.Stats.Stamina)); }
        protected void RestoreWill()                    { _action.AddAction(new RestoreAction(Hero, Hero.Stats.Will)); }
        protected void RestoreIntellect()               { _action.AddAction(new RestoreAction(Hero, Hero.Stats.Intellect)); }
        protected void RestoreCharisma()                { _action.AddAction(new RestoreAction(Hero, Hero.Stats.Charisma)); }

        protected void GainStrength()                   { _action.AddAction(new GainStatAction(Hero, Hero.Stats.Strength)); }
        protected void GainAgility()                    { _action.AddAction(new GainStatAction(Hero, Hero.Stats.Agility)); }
        protected void GainStamina()                    { _action.AddAction(new GainStatAction(Hero, Hero.Stats.Stamina)); }
        protected void GainWill()                       { _action.AddAction(new GainStatAction(Hero, Hero.Stats.Will)); }
        protected void GainIntellect()                  { _action.AddAction(new GainStatAction(Hero, Hero.Stats.Intellect)); }
        protected void GainCharisma()                   { _action.AddAction(new GainStatAction(Hero, Hero.Stats.Charisma)); }

        protected void GainAll()
        {
            GainStrength();
            GainAgility();
            GainStamina();
            GainWill();
            GainIntellect();
            GainCharisma();
        }

        protected void SwapStrength()                   { _action.AddAction(new SwapStatAction(Hero, Hero.Stats.Strength)); }
        protected void SwapAgility()                    { _action.AddAction(new SwapStatAction(Hero, Hero.Stats.Agility)); }
        protected void SwapStamina()                    { _action.AddAction(new SwapStatAction(Hero, Hero.Stats.Stamina)); }
        protected void SwapWill()                       { _action.AddAction(new SwapStatAction(Hero, Hero.Stats.Will)); }
        protected void SwapIntellect()                  { _action.AddAction(new SwapStatAction(Hero, Hero.Stats.Intellect)); }
        protected void SwapCharisma()                   { _action.AddAction(new SwapStatAction(Hero, Hero.Stats.Charisma)); }
        
        protected void Bolt(string noun)
        {
            if (!_target.HasValue) throw new InvalidOperationException("Cannot use the Bolt() script with Items that do not have a target.");

            _action.AddAction(new ElementBoltAction(_entity, _target.Value, new Noun(noun), _item.Type.Attack));
        }

        protected void Beam(string noun)
        {
            if (!_target.HasValue) throw new InvalidOperationException("Cannot use the Beam() script with Items that do not have a target.");

            _action.AddAction(new ElementBeamAction(_entity, _target.Value, new Noun(noun), _item.Type.Attack));
        }

        protected void Ball(string noun, int radius)
        {
            if (!_target.HasValue) throw new InvalidOperationException("Cannot use the Ball() script with Items that do not have a target.");

            _action.AddAction(new ElementBallAction(_entity, _target.Value, radius, new Noun(noun), _item.Type.Attack));
        }

        protected void Cone(string noun, int radius)
        {
            if (!_target.HasValue) throw new InvalidOperationException("Cannot use the Cone() script with Items that do not have a target.");

            _action.AddAction(new ElementConeAction(_entity, _target.Value, radius, new Noun(noun), _item.Type.Attack));
        }

        protected void BallSelf(string noun, int radius)
        {
            //### bob: hack. figure out where the item is
            Vec pos = _item.Position;
            if (_action.Game.Hero.Inventory.Contains(_item) ||
                _action.Game.Hero.Equipment.Contains(_item))
            {
                pos = _action.Game.Hero.Position;
            }

            _action.AddAction(new ElementBallAction(_entity, pos, radius, new Noun(noun), _item.Type.Attack));
        }

        #endregion

        private Entity _entity;
        private Item _item;
        private Action _action;
        private Vec? _target;
    }
}
